package javamobile.model;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javamobile.util.Utilities;

import org.mybeans.dao.DAOException;
import org.mybeans.factory.BeanFactory;
import org.mybeans.factory.BeanFactoryException;
import org.mybeans.factory.BeanTable;
import org.mybeans.factory.MatchArg;
import org.mybeans.factory.RollbackException;
import org.mybeans.factory.Transaction;

public class DAOFactory {
	private BeanFactory<User> userFactory;
	private BeanFactory<Friendship> friendshipFactory;
	private BeanFactory<PendingFriendship> pendingFriendshipFactory;
	private BeanFactory<Bill> billFactory;
	private BeanFactory<BillSplit> billSplitFactory;
	
	public DAOFactory (String jdbcDriverName, String jdbcURL) throws DAOException {
		try {
			BeanTable<User> userTable = BeanTable.getSQLInstance(User.class, "user", jdbcDriverName, jdbcURL);
			BeanTable<Friendship> friendshipTable = BeanTable.getSQLInstance(Friendship.class, "friendship", jdbcDriverName, jdbcURL);
			BeanTable<PendingFriendship> pendingFriendshipTable = BeanTable.getSQLInstance(PendingFriendship.class, "pendingfriendship", jdbcDriverName, jdbcURL);
			BeanTable<Bill> billTable = BeanTable.getSQLInstance(Bill.class, "bill", jdbcDriverName, jdbcURL);
			BeanTable<BillSplit> billSplitTable = BeanTable.getSQLInstance(BillSplit.class, "billsplit", jdbcDriverName, jdbcURL);
		
			if (!userTable.exists()) userTable.create("userID");
			if (!friendshipTable.exists()) friendshipTable.create("friendshipID");
			if (!pendingFriendshipTable.exists()) pendingFriendshipTable.create("pendingFriendshipID");
			if (!billTable.exists()) billTable.create("billID");
			if (!billSplitTable.exists()) billSplitTable.create("billSplitID");
			
			userTable.setIdleConnectionCleanup(true);
			friendshipTable.setIdleConnectionCleanup(true);
			pendingFriendshipTable.setIdleConnectionCleanup(true);
			billTable.setIdleConnectionCleanup(true);
			billSplitTable.setIdleConnectionCleanup(true);
			
			userFactory = userTable.getFactory();
			friendshipFactory = friendshipTable.getFactory();
			pendingFriendshipFactory = pendingFriendshipTable.getFactory();
			billFactory = billTable.getFactory();
			billSplitFactory = billSplitTable.getFactory();
		} catch (BeanFactoryException e) {
		throw new DAOException(e);
		}
	}
	
	public User lookupUser (String email) throws DAOException {
		try {
			Transaction.begin();
			User[] ul = userFactory.match(MatchArg.equals("email",email));
			User user = (ul.length==0 ? null : ul[0]);
			Transaction.commit();
			return user;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public User lookupUser (int userID) throws DAOException {
		try {
			Transaction.begin();
			User user = userFactory.lookup(userID);
			Transaction.commit();
			return user;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public int createUser(User user) throws DAOException {
		try {
			Transaction.begin();
			User dbUser = userFactory.create();
			userFactory.copyInto(user, dbUser);
			PendingFriendship[] pfl = pendingFriendshipFactory.match(MatchArg.equals("friendEmail", user.getEmail()));
			for (PendingFriendship pf:pfl) {
				pf.setFriendID(dbUser.getUserID());
			}
			Transaction.commit();
			return dbUser.getUserID();
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public Map<String,String> getFriendsList (int userID) throws DAOException {
		try {
			Transaction.begin();
			Map<String,String> map = new LinkedHashMap<String, String>();
			Friendship[] fl = friendshipFactory.match(MatchArg.equals("userID", userID));
			for (Friendship f:fl) {
				User u = userFactory.lookup(f.getFriendID());
				map.put(u.getEmail(),u.getFirstName()+" "+u.getLastName());
			}
			Friendship[] f2 = friendshipFactory.match(MatchArg.equals("friendID", userID));
			for (Friendship f:f2) {
				User u = userFactory.lookup(f.getUserID());
				map.put(u.getEmail(),u.getFirstName()+" "+u.getLastName());
			}
			Transaction.commit();
			return map;
		}	catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public Map<String, String> getPendingList (int userID) throws DAOException {
		try {
			Transaction.begin();
			Map<String, String>map = new LinkedHashMap<String, String>();
			
			PendingFriendship[] pfl1 = pendingFriendshipFactory.match(MatchArg.equals("friendID",userID));
			for (PendingFriendship p:pfl1) {
				User u = userFactory.lookup(p.getUserID());
				map.put(u.getEmail()+":"+u.getFirstName()+" "+u.getLastName(), "Received");
			}
			PendingFriendship[] pfl2 = pendingFriendshipFactory.match(MatchArg.equals("userID",userID));
			for (PendingFriendship p:pfl2) {
				String key = "";
				if (p.getFriendID() < 0) {
					key = p.getFriendEmail()+":";
				}
				else {
					User u = userFactory.lookup(p.getFriendID());
					key = p.getFriendEmail()+":"+u.getFirstName()+" "+u.getLastName();
				}
				map.put(key, "Sent");
			}
			Transaction.commit();
			return map;
		}	catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public String inviteFriends (int userID, String email) throws DAOException {
		try {
			String list = "";
			Transaction.begin();
			boolean valid = true;
			String result = "";
			PendingFriendship pf = null;
			
			// Check if the user is in our database; request if he is, invite if not
			User[] ul = userFactory.match(MatchArg.equals("email",email));
			if (ul.length>1)
				throw new DAOException("Internal Error. Duplicate email address.");
			User user = (ul.length==0 ? null : ul[0]);

				PendingFriendship[] pfl = pendingFriendshipFactory.match(MatchArg.and(MatchArg.equals("userID", userID),MatchArg.endsWithIgnoreCase("friendEmail", email)));
				if (pfl.length!=0) {
					valid = false;
					list = "You have already sent an invitation to " + email + ". Request is not sent.";
				}
				else if (user == null) {
					pf = new PendingFriendship (userID, email, -1);
				}
				else {
					if (user.getUserID() == userID) {
						valid = false;
						list = "You can't send an invitation to yourself. Request is not sent.";
					}
				
					PendingFriendship[] pfl2 = pendingFriendshipFactory.match(MatchArg.and(MatchArg.equals("userID", user.getUserID()),MatchArg.equals("friendID", userID)));
					if (pfl2.length!=0) {
						valid = false;
						list = email + " has already sent an invitation to you. Please check pending requests. Request is not sent.";
					}
				
					Friendship[] fl = friendshipFactory.match(MatchArg.and(MatchArg.equals("userID", user.getUserID()),MatchArg.equals("friendID", userID)));
					Friendship[] fl2 = friendshipFactory.match(MatchArg.and(MatchArg.equals("friendID", user.getUserID()),MatchArg.equals("userID", userID)));	
					if (fl.length!=0 || fl2.length!=0) {
						valid = false;
						list = email + " is already a friend of yours. Request is not sent.";
					}
				
				
					pf = new PendingFriendship (userID, email, user.getUserID());
				}

			
				if (valid) {
					PendingFriendship dbPendingFriendship = pendingFriendshipFactory.create();
					pendingFriendshipFactory.copyInto(pf, dbPendingFriendship);
					result = "success";
				}
				else {
					result = list;
				}
				Transaction.commit();

			return result;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public void acceptRequest (int userID, String email) throws DAOException {
		try {
			Transaction.begin();
			User[] ul = userFactory.match(MatchArg.equals("email", email));
			if (ul.length!=1)
				throw new DAOException("Error when retrieving User "+email+".");
			User u = ul[0];
			PendingFriendship[] pfl = pendingFriendshipFactory.match(MatchArg.and(MatchArg.equals("userID", u.getUserID()), MatchArg.equals("friendID", userID)));
			if (pfl.length!=1)
				throw new DAOException("Error when retrieving the invitation.");
			PendingFriendship pf = pfl[0];
			pendingFriendshipFactory.delete(pf.getPendingFriendshipID());
				
			Friendship f = new Friendship(userID, u.getUserID());
			Friendship dbFriendship = friendshipFactory.create();
			friendshipFactory.copyInto(f, dbFriendship);
			Transaction.commit();
		}
		catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public int saveBill (Bill bill, BillSplit[] splitterList) throws DAOException {
		try {
			Transaction.begin();
			Bill dbBill = billFactory.create();
			billFactory.copyInto(bill, dbBill);
			int billID = dbBill.getBillID();
			for (BillSplit bs:splitterList) {
				BillSplit dbBillSplit = billSplitFactory.create();
				bs.setBillID(billID);
				billSplitFactory.copyInto(bs, dbBillSplit);
			}
			Transaction.commit();
			return billID;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public void sendBill (int billID) throws DAOException {
		try {
			Transaction.begin();
			Bill bill = billFactory.lookup(billID);
			bill.setState(BillState.SENT);
			
			BillSplit[] bsl = billSplitFactory.match(MatchArg.equals("billID", billID));
			for (BillSplit bs:bsl) {
				bs.setState(BillState.SENT);
			}
			Transaction.commit();
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public List<Bill> getOpenBillList (int userID) throws DAOException {
		try {
			Transaction.begin();
			List<Bill> list = new ArrayList<Bill>();
			BillSplit[] bsl = billSplitFactory.match(MatchArg.and(MatchArg.equals("debtorID", userID), 
					MatchArg.equals("state", BillState.SENT)));
			for (BillSplit bs:bsl) {
				Bill b = billFactory.lookup(bs.getBillID());
				list.add(b);
			}
			Bill[] bl2 = billFactory.match(MatchArg.and(MatchArg.equals("payerID", userID), 
					MatchArg.or(MatchArg.equals("state", BillState.SENT) ) )   );
			for (Bill b:bl2) {
				list.add(b);
			}
			Transaction.commit();
			return list;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public List<Bill> getClosedBillList (int userID) throws DAOException {
		try {
			Transaction.begin();
			List<Bill> list = new ArrayList<Bill>();
			BillSplit[] bsl = billSplitFactory.match(MatchArg.and(MatchArg.equals("debtorID", userID), 
					 MatchArg.equals("state", BillState.PAIDOFF)) )    ;
			for (BillSplit bs:bsl) {
				Bill b = billFactory.lookup(bs.getBillID());
				list.add(b);
			}
			Bill[] bl2 = billFactory.match(MatchArg.and(MatchArg.equals("payerID", userID), 
					MatchArg.equals("state", BillState.PAIDOFF)) )   ;
			for (Bill b:bl2) {
				list.add(b);
			}
			Transaction.commit();
			return list;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public Map<Integer, List<Object>> iCollect (int userID) throws DAOException {
		try {
			Transaction.begin();
			Map<Integer, List<Object>> map = new LinkedHashMap<Integer, List<Object>>();
			BillSplit[] bsl = billSplitFactory.match(MatchArg.and(MatchArg.equals("payerID", userID), 
					MatchArg.or( MatchArg.equals("State", BillState.SENT)) ));
			for (BillSplit bs:bsl) {
				User u = userFactory.lookup(bs.getDebtorID());
				List<Object> list = new ArrayList<Object>();
				list.add(u.getFirstName()+" "+u.getLastName());
				List<Object> temp = map.get(u.getUserID());
				if (temp!=null) {
					double amount = (double)temp.get(1);
					amount += bs.getAmount();
					list.add(amount);
					map.put(u.getUserID(), list);
				}
				else {
					list.add(bs.getAmount());
					map.put(u.getUserID(), list);
				}
			}
			Transaction.commit();
			return map;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public Map<Integer, List<Object>> iOwe (int userID) throws DAOException {
		try {
			Transaction.begin();
			Map<Integer, List<Object>> map = new LinkedHashMap<Integer, List<Object>>();
			BillSplit[] bsl = billSplitFactory.match(MatchArg.and(MatchArg.equals("debtorID", userID), 
					MatchArg.or( MatchArg.equals("State", BillState.SENT)) ));
			for (BillSplit bs:bsl) {
				User u = userFactory.lookup(bs.getPayerID());
				List<Object> list = new ArrayList<Object>();
				list.add(u.getFirstName()+" "+u.getLastName());
				List<Object> temp = map.get(u.getUserID());
				if (temp!=null) {
					double amount = (double)temp.get(1);
					amount += bs.getAmount();
					list.add(amount);
					map.put(u.getUserID(), list);
				}
				else {
					list.add(bs.getAmount());
					map.put(u.getUserID(), list);
				}
			}
			Transaction.commit();
			return map;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public List<ArrayList<Object>> iHistory(int userID) throws DAOException {
		try {
			Transaction.begin();
			Calendar start = Calendar.getInstance();
			final int NUMBEROFMONTH = 6;
			start.add(Calendar.MONTH, -NUMBEROFMONTH);
			Calendar now = Calendar.getInstance();

			Map <Integer, Double> owed = new HashMap <Integer, Double> ();
			for (int i=0; i<12; i++) {
				owed.put(i, 0.0);
			}
			BillSplit[] owe_billList = billSplitFactory.match(MatchArg.and(MatchArg.equals("debtorID", userID), 
					MatchArg.or(MatchArg.equals("state", BillState.PAIDOFF)) ) );			
			for (BillSplit bs:owe_billList) {
				Bill b = billFactory.lookup(bs.getBillID());
				Date d;
				DateFormat df = new SimpleDateFormat ("MM/dd/yyyy");
				try {
					d = df.parse(b.getDate());
				} catch (ParseException e) {
					throw new DAOException(e);
				}
				Calendar thisDate = Calendar.getInstance();
				thisDate.setTime(d);
				if (thisDate.before(now) && thisDate.after(start)) {
					Double amount_owe = owed.get(thisDate.get(Calendar.MONTH));
					owed.put(thisDate.get(Calendar.MONTH), bs.getAmount()+amount_owe);
				}
			}
			
			Map <Integer, Double> collect = new HashMap <Integer, Double> ();
			for (int i=0; i<12; i++) {
				collect.put(i, 0.0);
			}
			Bill[] collect_billList = billFactory.match(MatchArg.and(MatchArg.equals("payerID", userID), 
					MatchArg.equals("state", BillState.PAIDOFF)) )   ;		
			for (Bill b:collect_billList) {
				Date d;
				DateFormat df = new SimpleDateFormat ("MM/dd/yyyy");
				try {
					d = df.parse(b.getDate());
				} catch (ParseException e) {
					throw new DAOException(e);
				}
				Calendar thisDate = Calendar.getInstance();
				thisDate.setTime(d);
				if (thisDate.before(now) && thisDate.after(start)) {
					Double amount_collect = collect.get(thisDate.get(Calendar.MONTH));
					collect.put(thisDate.get(Calendar.MONTH), b.getAmount()+amount_collect);
				}
			}
			List<ArrayList<Object>> list = new ArrayList<ArrayList<Object>>();
			for (int month = start.get(Calendar.MONTH); month<=now.get(Calendar.MONTH); month++) {
				ArrayList<Object> l = new ArrayList<Object> ();
				l.add(Utilities.translateToMonth(month));
				l.add(owed.get(month));
				l.add(collect.get(month));
				list.add(l);
			}
			Transaction.commit();
			return list;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public boolean hasRightToViewBill (int billID, int userID) throws DAOException {
		try {
			Transaction.begin();
			BillSplit[] bsl1 = billSplitFactory.match(MatchArg.and(MatchArg.equals("billID", billID), MatchArg.equals("debtorID", userID)));
			BillSplit[] bsl2 = billSplitFactory.match(MatchArg.and(MatchArg.equals("billID", billID), MatchArg.equals("payerID", userID)));
			Transaction.commit();
			if (bsl1.length==0 && bsl2.length==0)
				return false;
			else
				return true;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public BillSplit[] lookupBillSplit (int billID) throws DAOException {
		try {
			Transaction.begin();
			BillSplit[] bs = billSplitFactory.match(MatchArg.equals("billID", billID));
			Transaction.commit();
			return bs;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public Map<String, Object> viewBill (int billID, int userID) throws DAOException {
		try {
			Bill b = billFactory.lookup(billID);
			BillSplit[] bsl = lookupBillSplit(billID);
			if (!hasRightToViewBill(billID,userID))
				throw new DAOException("You has no right to view this bill.");
			Transaction.begin();
			if (b == null)
				throw new DAOException("No bill found with id = "+billID);
			Map<String, Object> map = new LinkedHashMap<String, Object> ();
			map.put("description", b.getDescription());
			map.put("date", b.getDate());
			map.put("amount", String.valueOf(b.getAmount()));
			map.put("location", b.getLocation());
			User payer = userFactory.lookup(b.getPayerID());
			if (userID == b.getPayerID()) {
				map.put("paidBy", "me");
			}
			else {
				map.put("paidBy", payer.getFirstName()+" "+payer.getLastName());
			}
			List<ArrayList<Object>> list = new ArrayList<ArrayList<Object>>();
			for (BillSplit bs: bsl) {
				ArrayList<Object> l = new ArrayList<Object>();
				User debtor = userFactory.lookup(bs.getDebtorID());
				l.add(bs.getDebtorID());
				l.add(debtor.getFirstName()+" "+debtor.getLastName());
				l.add(bs.getAmount());
				l.add(bs.getState().name());
				if (userID == b.getPayerID()) {// this user is a sender
						l.add("");
				}
				else {
					if (bs.getState() == BillState.SENT && bs.getDebtorID() == userID) {
						l.add("Pay-off");
					}
					else
						l.add("");
				}
				list.add(l);
			}
			map.put("list", list);
			Transaction.commit();
			return map;
		} catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	}
	
	public void payoff (int billID, int userID) throws DAOException {
		try {
			Transaction.begin();
			BillSplit[] bsl = billSplitFactory.match(MatchArg.and(MatchArg.equals("billID", billID), MatchArg.equals("debtorID", userID)));
			if (bsl.length!=1)
				throw new DAOException("Error when retrieving the bill.");
			BillSplit bs = bsl[0];
			if (bs.getState() != BillState.SENT)
				throw new DAOException("Bill is in wrong state. Can't pay off the bill.");
			bs.setState(BillState.PAIDOFF);
			
			BillSplit[] bsl2 = billSplitFactory.match(MatchArg.equals("billID", billID));
			boolean all_confirmed = true;
			for (BillSplit bs2:bsl2) {
				if (bs2.getState()!=BillState.PAIDOFF)
					all_confirmed = false;
			}
			Bill b = billFactory.lookup(billID);
			if (all_confirmed)
				b.setState(BillState.PAIDOFF);
			Transaction.commit();
		}
		catch (RollbackException e) {
			throw new DAOException(e);
		} finally {
			if (Transaction.isActive()) Transaction.rollback();
		}
	} 
}
